import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;

public class Juego {

	private Tablero tablero;
	private ArrayList<Jugador> jugadores;
	private Jugador jugadorActual;
	private int indiceActual;
	private Jugador ganador;
	private ArrayList<Personaje> personajes;
	
	// constructor que recibe la lista de nombres de los jugadores
	public Juego(ArrayList<String> nombreJugadores) {
		// creo la lista de jugadores
		this.jugadores = new ArrayList<Jugador>();

		// por cada nombre creo un jugador con dicho nombre
		for (String s : nombreJugadores) {
			this.jugadores.add(new Jugador(s));
		}

		// inicializa el tablero
		this.tablero = new Tablero(this.jugadores.size());
		
		//reparte las casillas
		int cantCasillas = this.tablero.cantidadCiudades();
		for(Jugador j : this.jugadores){
			j.setCantCasillas(cantCasillas);
		}
		
		// mezclar los jugadores
		Collections.shuffle(this.jugadores);	
		
		//crea los personajes
		this.crearPersonajes();
	}
	
	//GETTERS AUXILIARES
	public String verGanador(){
		return this.ganador.getNombre();
	}
	
	public ArrayList<Region> getRegiones(){
		return this.tablero.getRegiones();
	}
	
	public ArrayList<Ruta> getRutas(){
		return this.tablero.getRutas();
	}
	
	//devuelve la cuadrilla visible del tablero
	public ArrayList<Carta> getCuadrilla(){
		return this.tablero.getCuadrilla();
	}
	
	public Jugador getGanador(){
		return this.ganador;
	}
	
	//METODOS GLOBALES	
	public void terminarJuego(){		
		this.entregarFichaFin();		
		this.setearGanador();
	}		
	
	public ArrayList<Jugador> getPerdedores(){
		ArrayList<Jugador> perdedores = new ArrayList<Jugador>();
		for(Jugador j : perdedores){
			if(j != this.ganador){
				perdedores.add(j);
			}
		}
		return perdedores;
	}
	
	//avanza de turno al siguiente jugador
	public void siguienteJugador() {

		// si es el primer turno del partido setea al jugador actual como el
		// primero de la lista
		if (this.jugadorActual == null) {
			// setea el jugador inicial del primero de la lista
			this.jugadorActual = this.jugadores.get(0);
			this.indiceActual = 0;
		} else {			
			// avanza al siguiente jugador
			this.indiceActual++;
			if (this.indiceActual >= this.jugadores.size()) {
				this.indiceActual = 0;
			}
			this.jugadorActual = this.jugadores.get(this.indiceActual);			
		}
		
		this.jugadorActual.siguienteEtapa();

	}				
	
	//verifica si termino la partida
	public boolean terminoPartida() {
		int cantCiudades = this.tablero.cantidadCiudades();		
		int contador;		
		for(Jugador j : this.jugadores){
			contador = 0;
			for(Ciudad c : this.tablero.getCiudades()){
				if(c.tieneCasilla(j)){
					contador++;
				}
			}
			if(contador >= cantCiudades * 0.75){				
				return true;
			}
		}
		
		return false;
	}
	
	//metodo para renovar la cuadrilla con 6 cartas nuevas
	public void renovarCuadrilla(){
		this.tablero.renovarCuadrilla();
	}
	
	//METODOS DEL JUGADOR ACTUAL
	public String getNombre(){
		return this.jugadorActual.getNombre();
	}
	
	public int getNumEtapa(){
		return this.jugadorActual.getNumEtapa();
	}
	
	public int getPuntos(){
		return this.jugadorActual.getPuntos();
	}
	
	public ArrayList<Ficha> getFichas(){
		return this.jugadorActual.getFichas();
	}
	
	public int getCasillasRestantes(){
		return this.jugadorActual.getCantCasillas();
	}
	
	public String getStatusCasillas(){
		return this.jugadorActual.getCantCasillasUtilizadas() + "/" + this.tablero.cantidadCiudades();
	}
	
	// cierra el turno del jugador actual
	public void cerrarTurno(){		
		this.jugadorActual.cerrarTurno();
	}
	
	//verifica si el camino es todo dentro de una misma region
	public boolean caminoUniRegion(){
		return this.jugadorActual.getCaminoActual().esUniRegion();
	}
	
	//levanta una carta especifrica de la cuadrilla
	public void levantarCartaCuadrilla(int posicion){
		this.jugadorActual.levantarCarta(this.tablero.sacarCartaCuadrilla(posicion));
	}
	
	//levanta una carta del mazo
	public void levantarCartaMazo(){		
		this.jugadorActual.levantarCarta(this.tablero.sacarCartaMazo());
	}
	
	//valida si en la mano tiene una carta para esa posicion
	public boolean tieneCarta(int posicion){
		return posicion >= 0 && this.jugadorActual.getMano().tamano() >= posicion;
	}
	
	//verifica si el tama�o del camino es mayor o igual a 3
	public boolean puedeCerrarCamino(){
		return this.jugadorActual.getCaminoActual().tamano() >= 3;
	}
	
	//agrega al camino una carta	
	public void agregarAlCamino(int posicion) throws JuegoException{
		//valida si la ciudad es limitrofe al camino y que no sea una que ya contiene el camino
		if(this.jugadorActual.getMano().tamano() >= posicion){
			Carta c = this.jugadorActual.getMano().getCarta(posicion);
			if(this.puedeAgregarAlCamino(c) && !this.jugadorActual.getCaminoActual().contieneCiudad(c)){
				if(this.jugadorActual.getCaminoActual().estaVacio()){
					this.jugadorActual.agregarAlCamino(c,true);
				}else if(this.tablero.sonLimitrofe(c.getCiudad(), this.jugadorActual.getCaminoActual().getInicio())){
					this.jugadorActual.agregarAlCamino(c,true);
				}else{
					this.jugadorActual.agregarAlCamino(c,false);
				}
				
				//saca la carta de la mano
				this.jugadorActual.getMano().remover(posicion);
			}else{
				throw new JuegoException("La ciudad no puede ser agregada al camino");
			}
		}else{
			throw new JuegoException("esa posicion no esta en la mano");
		}
	}
	
	//cierra el camino actual
	public void cerrarCaminoSimple() throws MazoCaminoException{
		ArrayList<Casilla> casillasUtilizadas = new ArrayList<Casilla>();
		
		if(this.caminoUniRegion()){
			for(Carta c : this.jugadorActual.getCaminoActual().getCartas()){
				if(!c.getCiudad().tieneCasilla(this.jugadorActual)){
					Casilla nuevaCasilla = this.jugadorActual.usarCasilla();					
					c.getCiudad().agregarCasilla(nuevaCasilla);
					casillasUtilizadas.add(nuevaCasilla);
				}
			}
		}else{
			throw new MazoCaminoException("Diferentes regiones dentro del camino");
		}
				
		this.evaluarPremios(casillasUtilizadas);
		
		//descarta el camino
		this.descartarCamino();
	}
	
	//cierra el camino cuando el camino pasa por mas de una region
	public void cerrarCaminoCompuesto(ArrayList<Integer> indicesCiudades) throws MazoCaminoException{
		ArrayList<Casilla> casillasUtilizadas = new ArrayList<Casilla>();
		
		HashSet<Region> regiones = new HashSet<Region>();
		HashSet<Region> regionesCamino = this.jugadorActual.getCaminoActual().getRegiones();
		
		//verifica que el numero de ciudades seleccionadas sea el mismo que las regiones por donde pasa el camino
		if(indicesCiudades.size() != regionesCamino.size()){
			throw new MazoCaminoException("Deben ser seleccionadas tantas ciudades como regiones del camino");
		}
		
		//verifica que las ciudades que se pasan esten todas en el camino
		for(int ind : indicesCiudades){
			if(ind < 0 || ind >= this.jugadorActual.getCaminoActual().tamano()){
				throw new MazoCaminoException("las ciudades deben ser del camino");
			}
		}
		
		for(int ind : indicesCiudades){
			Ciudad c = this.jugadorActual.getCaminoActual().getCarta(ind).getCiudad();		
			//verifica que haya elegido una ciudad por region
			if(!regiones.contains(c.getRegion())){
				regiones.add(c.getRegion());					
			}else{
				throw new MazoCaminoException("Solo puede seleccionar una ciudad por region");
			}
		}
			
		//si no hubo errores agrega las casillas en las ciudades seleccionadas
		for(int ind : indicesCiudades){
			Ciudad c = this.jugadorActual.getCaminoActual().getCarta(ind).getCiudad();
			if(!c.tieneCasilla(this.jugadorActual)){
				Casilla nuevaCasilla = this.jugadorActual.usarCasilla();
				c.agregarCasilla(nuevaCasilla);
				casillasUtilizadas.add(nuevaCasilla);
			}
		}
		
		this.evaluarPremios(casillasUtilizadas);
		
		//descarta el camino
		this.descartarCamino();
	}
	
	//avanza de etapa
	public void avanzarEtapa(){
		this.jugadorActual.siguienteEtapa();
	}
	
	//muestra la mano del jugador
	public ArrayList<Carta> verMano(){
		return this.jugadorActual.getMano().getCartas();
	}			
	
	//muestra el camino del jugador
	public ArrayList<Carta> verCamino(){
		return this.jugadorActual.getCaminoActual().getCartas();
	}
	
	//cierra el turno 
	public void pasarTurno(){
		this.jugadorActual.cerrarTurno();
	}
	
	//utiliza algun personaje de ayuda
	public void usarAyuda(Personaje ayudante){
		this.jugadorActual.setAyudante(ayudante);
	}
	
	//verifica si ya utilizo la ayuda
	public boolean pidioAyuda(){
		return this.jugadorActual.getAyudante() != null;
	}
	
	public String getAyudanteUsado(){
		if(this.jugadorActual.getAyudante() != null){
			return this.jugadorActual.getAyudante().getNombre();
		}else{
			return "";
		}
	}
	
	//metodo que devuelve el listado de personajes para una situacion dada
	public ArrayList<Personaje> getPersonajes(int etapa, boolean principio){
		//lista a retornar
		ArrayList<Personaje> personajesARetornar = new ArrayList<Personaje>();
		//se recorre la lista de personajes del juego y agrega los que cumplan las condiciones requeridas por el metodo
		for(Personaje pj : this.personajes){
			if(pj.getEtapa() == etapa && pj.principio() == principio){
				personajesARetornar.add(pj);
			}
		}
		
		//retorna la lista
		return personajesARetornar;
	}
	
	//METODOS PRIVADOS
	private boolean puedeAgregarAlCamino(Carta c) {
		if(this.jugadorActual.getCaminoActual().estaVacio()){
			return true;
		}else{
			return this.tablero.sonLimitrofe(c.getCiudad(), this.jugadorActual
				.getCaminoActual().getInicio())
				|| this.tablero.sonLimitrofe(c.getCiudad(), this.jugadorActual
						.getCaminoActual().getFin());
		}
	}

	public void descartarCamino(){
		//agrega las cartas del camino en el mazo de descarte
		this.tablero.getDescarte().agregarCartas(this.jugadorActual.getCaminoActual().getCartas());
		//vacia el camino actual
		this.jugadorActual.getCaminoActual().limpiar();		
	}
	
	//metodo que trae una ficha de longitud determinada
	private FichaLongitud traerFichaLongitud(int longitud){		
		for(FichaLongitud f : this.tablero.getPremiosLong()){
			if(f.getLongitud() <= longitud && !this.jugadorActual.contieneFicha(f) ){
				this.tablero.getPremiosLong().remove(f);
				return f;				
			}
		}
		return null;
	}
	
	//metodo que devuelve las fichas para las regiones que son pasadas por parametro siempre y cuando corresponda
	private ArrayList<FichaRegiones> traerFichasRegiones(ArrayList<Region> regiones){		
		//lista de fichas a retornar
		ArrayList<FichaRegiones> fichas = new ArrayList<FichaRegiones>();
		
		//por cada region evalua si la region esta completa de fichas de ese jugador		
		for(Region reg : regiones){			
			FichaRegiones fRegion = null;
			//recorre la lista de fichas de regiones del tablero hasta encontrar la de la region buscada
			for(FichaRegiones fReg : this.tablero.getPremiosReg()){
				//si se encontro la region y el jugador no la contiene entonces setea la ficha
				if(fReg.getRegion() == reg && !this.jugadorActual.contieneFicha(fReg)){
					fRegion = fReg;					
					break;
				}
			}
			
			//si se logro setear la ficha, lo cual quiere decir que el jugador todavia no gano esa ficha entonces evalua si la puede ganar
			if(fRegion != null){
				//recorre la lista de ciudades de la region para verificar si ya posee casillas en todas sus ciudades
				boolean regionCompletada = true;
				for(Ciudad c : reg.getCiudades()){
					//caso que alguna ciudad no contenga casilla del jugador se sale del bucle y se setea el bool en false
					if(!c.tieneCasilla(this.jugadorActual)){
						regionCompletada = false;
						break;
					}
				}
				
				//si completo la region entera entonces agrega la ficha a la lista y la remueve del tablero
				if(regionCompletada){
					fichas.add(fRegion);
					this.tablero.getPremiosReg().remove(fRegion);
				}
			}
		}
		
		return fichas;		
	}
	
	//metodo que trae una ficha de longitud
	private FichaTotalRegiones traerFichaTotalRegiones(){		
		for(FichaTotalRegiones f : this.tablero.getPremiosTotReg()){
			if(!this.jugadorActual.contieneFicha(f) ){
				for(Region reg : this.tablero.getRegiones()){
					boolean regionConCasilla = false;
					for(Ciudad c : reg.getCiudades()){
						if(c.tieneCasilla(this.jugadorActual)){
							regionConCasilla = true;
							break;
						}
					}
					if(!regionConCasilla){
						return null;
					}
				}
				this.tablero.getPremiosTotReg().remove(f);
				return f;
			}
		}
		return null;
	}
	
	private void evaluarPremios(ArrayList<Casilla> casillasUtilizadas){
		
		//fichas de longitud
		//si no esta vacia la lista
		if(!this.tablero.getPremiosLong().isEmpty()){
			int longCamino = this.jugadorActual.getCaminoActual().getLongitudCamino();
			
			FichaLongitud ficha = traerFichaLongitud(longCamino);			
			if(ficha != null){			
				this.jugadorActual.agregarFicha(ficha);
			}
		}
		
		//evaluar premios regiones
		if(!this.tablero.getPremiosReg().isEmpty()){
			ArrayList<Region> regiones = new ArrayList<Region>();
			for(Casilla c : casillasUtilizadas){
				regiones.add(c.getCiudad().getRegion());
			}
			
			ArrayList<FichaRegiones> fichasReg = traerFichasRegiones(regiones);
						
			for(FichaRegiones fReg : fichasReg){
				this.jugadorActual.agregarFicha(fReg);
			}			
		}
		
		//evaluar premios todas las regiones
		if(!this.tablero.getPremiosLong().isEmpty()){
			FichaTotalRegiones ficha = traerFichaTotalRegiones();			
			if(ficha != null){			
				this.jugadorActual.agregarFicha(ficha);
			}
		}
	}
	
	//entrega la ficha de finalizacion
	private void entregarFichaFin(){
		if(!this.tablero.getPremiosFin().isEmpty()){
			this.jugadorActual.agregarFicha(this.tablero.getPremiosFin().get(0));
		}
	}
	
	//calcula el ganador del juego
	private void setearGanador() {
		
		if(this.ganador == null){
			this.ganador = this.jugadores.get(0);
		}
		
		for(Jugador jug : this.jugadores){
			if(jug.getPuntos() > this.ganador.getPuntos()){
				this.ganador = jug;
			}
		}
	}	
	
	//metodo para crear los personajes base del juego
	private void crearPersonajes(){
		personajes = new ArrayList<Personaje>();
		this.personajes.add(new Administrador("Platon"));
		this.personajes.add(new Contratista("Arquimedes"));
		this.personajes.add(new CorredorPostal("Aristoteles"));
	}
}